#include <sdk.h>
#include <configurationpanel.h>
#include <M_Code_StyledTextCtrl.h>
#include <editorcolourset.h>
#include "abbreviations.h"
#include "abbreviationsconfigpanel.h"
#include <ccmanager.h>
#include <editor_hooks.h>
#include <sqplus.h>
#include <sc_base_types.h>

namespace {
  PluginRegistrant<Abbreviations> reg( _T( "Abbreviations" ) );
  const int idEditAutoComplete = XRCID( "idEditAutoComplete" );
}

Abbreviations* Abbreviations::m_Singleton = nullptr;

static const char* abbrev_xpm[] = {
  "16 16 7 1",
  "   c None",
  ".  c #B00000",
  "+  c #F70000",
  "@  c #DD3F3F",
  "#  c #FF3A39",
  "$  c #D56D6C",
  "%  c #F5A8A8",
  "                ",
  "                ",
  "          +.    ",
  "   %%#++++++    ",
  "  %%#++++++++   ",
  " $%@+++++++++   ",
  " %@++. +++++++  ",
  " #++.  +++####  ",
  " +++   .@$$%%$@ ",
  "++++            ",
  "+++.            ",
  "+++             ",
  "++.             ",
  "+..             ",
  "                ",
  "                "
};


BEGIN_EVENT_TABLE( Abbreviations, M_Code_Plugin )
  EVT_MENU( idEditAutoComplete, Abbreviations::OnEditAutoComplete )
  EVT_UPDATE_UI( idEditAutoComplete, Abbreviations::OnEditMenuUpdateUI )
END_EVENT_TABLE()

wxString defaultLanguageStr = _T( "--default--" );

Abbreviations::Abbreviations() {
  if( !Manager::LoadResource( _T( "Abbreviations.zip" ) ) ) {
    NotifyMissingFile( _T( "Abbreviations.zip" ) );
  }
  m_IsAutoCompVisible = false;
}

Abbreviations::~Abbreviations() {
}

void Abbreviations::OnAttach() {
  m_Singleton = this;
  LoadAutoCompleteConfig();
  RegisterScripting();
  EditorHooks::HookFunctorBase* myhook = new EditorHooks::HookFunctor<Abbreviations>( this, &Abbreviations::EditorEventHook );
  m_EditorHookId = EditorHooks::RegisterHook( myhook );
}

void Abbreviations::OnRelease( M_Code_unused bool appShutDown ) {
  UnregisterScripting();
  SaveAutoCompleteConfig();
  if( m_Singleton == this ) {
    m_Singleton = nullptr;
  }
  EditorHooks::UnregisterHook( m_EditorHookId, true );
  ClearAutoCompLanguageMap();
}

void Abbreviations::RegisterScripting() {
  Manager::Get()->GetScriptingManager();
  if( SquirrelVM::GetVMPtr() ) {
    SqPlus::RegisterGlobal( &Abbreviations::AutoComplete, "AutoComplete" );
  }
}

void Abbreviations::UnregisterScripting() {
  Manager::Get()->GetScriptingManager();
  HSQUIRRELVM v = SquirrelVM::GetVMPtr();
  if( v ) {
    sq_pushstring( v, "AutoComplete", -1 );
    sq_pushroottable( v );
    sq_deleteslot( v, -2, false );
    sq_poptop( v );
  }
}

void Abbreviations::OnEditAutoComplete( M_Code_unused wxCommandEvent& event ) {
  M_Code_Editor* editor = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( !editor ) {
    return;
  }
  M_Code_StyledTextCtrl* control = editor->GetControl();
  const AutoCompleteMap& acm = *GetCurrentACMap( editor );
  int curPos = control->GetCurrentPos();
  int startPos = control->WordStartPosition( curPos, true );
  const int endPos = control->WordEndPosition( curPos, true );
  const wxString keyword = control->GetTextRange( startPos, endPos );
  AutoCompleteMap::const_iterator acm_it = acm.find( keyword );
  if( acm_it != acm.end() ) {
    DoAutoComplete( editor );
  } else {
    wxArrayString items;
    for( acm_it = acm.begin(); acm_it != acm.end(); ++acm_it ) {
      if( acm_it->first.Lower().StartsWith( keyword ) ) {
        items.Add( acm_it->first + _T( "?0" ) );
      }
    }
    if( !items.IsEmpty() ) {
      control->ClearRegisteredImages();
      control->RegisterImage( 0, wxBitmap( abbrev_xpm ) );
      items.Sort();
      wxString itemsStr = GetStringFromArray( items, _T( " " ) );
      control->AutoCompSetSeparator( _T( ' ' ) );
      control->AutoCompSetTypeSeparator( _T( '?' ) );
      Manager::Get()->GetCCManager()->InjectAutoCompShow( endPos - startPos, itemsStr );
    }
    m_IsAutoCompVisible = control->AutoCompActive();
  }
}

void Abbreviations::OnEditMenuUpdateUI( wxUpdateUIEvent& event ) {
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  event.Enable( ed != NULL );
}

void Abbreviations::AutoComplete( M_Code_Editor& ed ) {
  if( Abbreviations::Get() ) {
    Abbreviations::Get()->DoAutoComplete( &ed );
  }
}

void Abbreviations::DoAutoComplete( M_Code_Editor* ed ) {
  if( !ed ) {
    return;
  }
  M_Code_StyledTextCtrl* control = ed->GetControl();
  if( !control ) {
    return;
  }
  if( control->AutoCompActive() ) {
    control->AutoCompCancel();
  }
  if( control->CallTipActive() ) {
    control->CallTipCancel();
  }
  m_IsAutoCompVisible = false;
  int curPos = control->GetCurrentPos();
  int wordStartPos = control->WordStartPosition( curPos, true );
  const int endPos = control->WordEndPosition( curPos, true );
  wxString keyword = control->GetTextRange( wordStartPos, endPos );
  wxString lineIndent = ed->GetLineIndentString( control->GetCurrentLine() );
  AutoCompleteMap* pAutoCompleteMap = GetCurrentACMap( ed );
  AutoCompleteMap::iterator it = pAutoCompleteMap->find( keyword );
  if( it != pAutoCompleteMap->end() ) {
    wxString code = it->second;
    code.Replace( _T( "\n" ), _T( '\n' ) + lineIndent );
    int macroPos = code.Find( _T( "$(" ) );
    while( macroPos != -1 ) {
      int macroPosEnd = macroPos + 2;
      int len = ( int )code.Length();
      while( macroPosEnd < len && code.GetChar( macroPosEnd ) != _T( ')' ) ) {
        ++macroPosEnd;
      }
      if( macroPosEnd == len ) {
        return;
      }
      wxString macroName = code.SubString( macroPos + 2, macroPosEnd - 1 );
      wxString macro = cbGetTextFromUser( _( "请输入文本 \"" ) + macroName + _T( "\":" ), _( "宏替换" ) );
      if( macro.IsEmpty() ) {
        return;
      }
      code.Replace( _T( "$(" ) + macroName + _T( ")" ), macro );
      macroPos = code.Find( _T( "$(" ) );
    }
    control->BeginUndoAction();
    control->SetSelectionVoid( wordStartPos, endPos );
    control->ReplaceSelection( wxEmptyString );
    curPos = wordStartPos;
    Manager::Get()->GetMacrosManager()->ReplaceMacros( code );
    if( control->GetEOLMode() == wxSCI_EOL_CRLF ) {
      code.Replace( _T( "\n" ), _T( "\r\n" ) );
    } else if( control->GetEOLMode() == wxSCI_EOL_CR ) {
      code.Replace( _T( "\n" ), _T( "\r" ) );
    }
    control->InsertText( curPos, code );
    int caretPos = code.Find( _T( '|' ) );
    if( caretPos != -1 ) {
      control->SetCurrentPos( curPos + caretPos );
      control->SetSelectionVoid( curPos + caretPos, curPos + caretPos + 1 );
      control->ReplaceSelection( wxEmptyString );
    }
    control->ChooseCaretX();
    control->EndUndoAction();
  }
}

void Abbreviations::LoadAutoCompleteConfig() {
  ClearAutoCompLanguageMap();
  AutoCompleteMap* pAutoCompleteMap;
  wxArrayString list = Manager::Get()->GetConfigManager( _T( "editor" ) )->EnumerateSubPaths( _T( "/auto_complete" ) );
  for( unsigned int i = 0; i < list.GetCount(); ++i ) {
    wxString langStr = Manager::Get()->GetConfigManager( _T( "editor" ) )->Read( _T( "/auto_complete/" ) + list[i] + _T( "/language" ), defaultLanguageStr );
    wxString name = Manager::Get()->GetConfigManager( _T( "editor" ) )->Read( _T( "/auto_complete/" ) + list[i] + _T( "/name" ), wxEmptyString );
    wxString code = Manager::Get()->GetConfigManager( _T( "editor" ) )->Read( _T( "/auto_complete/" ) + list[i] + _T( "/code" ), wxEmptyString );
    if( m_AutoCompLanguageMap.find( langStr ) == m_AutoCompLanguageMap.end() ) {
      m_AutoCompLanguageMap[langStr] = new AutoCompleteMap();
    }
    pAutoCompleteMap = m_AutoCompLanguageMap[langStr];
    if( name.IsEmpty() ) {
      continue;
    }
    wxString resolved;
    resolved.Alloc( code.Length() );
    for( size_t pos = 0; pos < code.Length(); ++pos ) {
      if( code[pos] == _T( '\\' ) && pos < code.Length() - 1 ) {
        ++pos;
        if( code[pos] == _T( 'n' ) ) {
          resolved += _T( "\n" );
        } else if( code[pos] == _T( 'r' ) ) {
          resolved += _T( "\r" );
        } else if( code[pos] == _T( 't' ) ) {
          resolved += _T( "\t" );
        } else if( code[pos] == _T( '\\' ) ) {
          resolved += _T( "\\" );
        } else {
          resolved += _T( "\\" );
          resolved += code[pos];
        }
      } else
      { resolved += code[pos]; }
    }
    resolved.Replace( _T( "\r\n" ), _T( "\n" ) );
    resolved.Replace( _T( "\r" ),   _T( "\n" ) );
    ( *pAutoCompleteMap )[name] = resolved;
  }
  if( m_AutoCompLanguageMap.find( defaultLanguageStr ) == m_AutoCompLanguageMap.end() ) {
    m_AutoCompLanguageMap[defaultLanguageStr] = new AutoCompleteMap();
  }
  pAutoCompleteMap = m_AutoCompLanguageMap[defaultLanguageStr];
  if( pAutoCompleteMap->empty() ) {
    ( *pAutoCompleteMap )[_T( "if" )] = _T( "if (|)\n\t;" );
    ( *pAutoCompleteMap )[_T( "ifb" )] = _T( "if (|)\n{\n\t\n}" );
    ( *pAutoCompleteMap )[_T( "ife" )] = _T( "if (|)\n{\n\t\n}\nelse\n{\n\t\n}" );
    ( *pAutoCompleteMap )[_T( "ifei" )] = _T( "if (|)\n{\n\t\n}\nelse if ()\n{\n\t\n}\nelse\n{\n\t\n}" );
    ( *pAutoCompleteMap )[_T( "guard" )] = _T( "#ifndef $(Guard token)\n#define $(Guard token)\n\n|\n\n#endif // $(Guard token)\n" );
    ( *pAutoCompleteMap )[_T( "while" )] = _T( "while (|)\n\t;" );
    ( *pAutoCompleteMap )[_T( "whileb" )] = _T( "while (|)\n{\n\t\n}" );
    ( *pAutoCompleteMap )[_T( "do" )] = _T( "do\n{\n\t\n} while (|);" );
    ( *pAutoCompleteMap )[_T( "switch" )] = _T( "switch (|)\n{\ncase :\n\tbreak;\n\ndefault:\n\tbreak;\n}\n" );
    ( *pAutoCompleteMap )[_T( "for" )] = _T( "for (|; ; )\n\t;" );
    ( *pAutoCompleteMap )[_T( "forb" )] = _T( "for (|; ; )\n{\n\t\n}" );
    ( *pAutoCompleteMap )[_T( "class" )] = _T( "class $(Class name)|\n{\npublic:\n\t$(Class name)();\n\t~$(Class name)();\nprotected:\nprivate:\n};\n" );
    ( *pAutoCompleteMap )[_T( "struct" )] = _T( "struct |\n{\n\t\n};\n" );
    ( *pAutoCompleteMap )[_T( "main" )] = _T( "#include <stdio.h>\n#include <stdlib.h>\n\nint main() {\n\n    printf(\"|\"); \n	system (\"pause\");\n    return 0;\n}" );
  }
  ExchangeTabAndSpaces( *pAutoCompleteMap );
  const wxString timeAndDate[9][2] = {
    { _T( "tday" ), _T( "$TDAY" ) },
    { _T( "tdayu" ), _T( "$TDAY_UTC" ) },
    { _T( "today" ), _T( "$TODAY" ) },
    { _T( "todayu" ), _T( "$TODAY_UTC" ) },
    { _T( "now" ), _T( "$NOW" ) },
    { _T( "nowl" ),   _T( "$NOW_L" ) },
    { _T( "nowu" ), _T( "$NOW_UTC" ) },
    { _T( "nowlu" ),  _T( "$NOW_L_UTC" ) },
    { _T( "wdu" ), _T( "$WEEKDAY_UTC" ) },
  };
  for( int i = 0; i < 9; ++i ) {
    if( pAutoCompleteMap->find( timeAndDate[i][0] ) == pAutoCompleteMap->end() ) {
      ( *pAutoCompleteMap )[timeAndDate[i][0]] = timeAndDate[i][1];
    }
  }
  wxString langFortran = _T( "Fortran" );
  if( m_AutoCompLanguageMap.find( langFortran ) == m_AutoCompLanguageMap.end() ) {
    m_AutoCompLanguageMap[langFortran] = new AutoCompleteMap();
  }
  pAutoCompleteMap = m_AutoCompLanguageMap[langFortran];
  if( pAutoCompleteMap->empty() ) {
    ( *pAutoCompleteMap )[_T( "if" )]  = _T( "if (|) then\n\t\nend if\n" );
    ( *pAutoCompleteMap )[_T( "do" )]  = _T( "do |\n\t\nend do\n" );
    ( *pAutoCompleteMap )[_T( "dw" )]  = _T( "do while (|)\n\t\nend do\n" );
    ( *pAutoCompleteMap )[_T( "sc" )]  = _T( "select case (|)\n\tcase ()\n\t\t\n\tcase default\n\t\t\nend select\n" );
    ( *pAutoCompleteMap )[_T( "fun" )] = _T( "function |()\n\t\nend function\n" );
    ( *pAutoCompleteMap )[_T( "sub" )] = _T( "subroutine |()\n\t\nend subroutine\n" );
    ( *pAutoCompleteMap )[_T( "mod" )] = _T( "module |\n\t\nend module\n" );
    ( *pAutoCompleteMap )[_T( "ty" )]  = _T( "type |\n\t\nend type\n" );
  }
  ExchangeTabAndSpaces( *pAutoCompleteMap );
}

void Abbreviations::ExchangeTabAndSpaces( AutoCompleteMap& map ) {
  const bool useTabs = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/use_tab" ), false );
  const int tabSize = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadInt( _T( "/tab_size" ), 4 );
  const wxString tabSpace = wxString( _T( ' ' ), tabSize );
  for( AutoCompleteMap::iterator it = map.begin(); it != map.end(); ++it ) {
    wxString& item = it->second;
    if( useTabs ) {
      item.Replace( tabSpace, _T( "\t" ), true );
    } else
    { item.Replace( _T( "\t" ), tabSpace, true ); }
  }
}

void Abbreviations::SaveAutoCompleteConfig() {
  Manager::Get()->GetConfigManager( _T( "editor" ) )->DeleteSubPath( _T( "/auto_complete" ) );
  AutoCompLanguageMap::iterator itlan;
  AutoCompleteMap::iterator it;
  int count = 0;
  for( itlan = m_AutoCompLanguageMap.begin(); itlan != m_AutoCompLanguageMap.end(); ++itlan ) {
    wxString langStr = itlan->first;
    wxString langStrLw = langStr.Lower();
    AutoCompleteMap* pAutoCompleteMap = itlan->second;
    for( it = pAutoCompleteMap->begin(); it != pAutoCompleteMap->end(); ++it ) {
      wxString code = it->second;
      code.Replace( _T( "\\" ),   _T( "\\\\" ) );
      code.Replace( _T( "\r\n" ), _T( "\\n" ) );
      code.Replace( _T( "\n" ),   _T( "\\n" ) );
      code.Replace( _T( "\r" ),   _T( "\\n" ) );
      code.Replace( _T( "\t" ),   _T( "\\t" ) );
      ++count;
      wxString key;
      if( !langStr.IsSameAs( defaultLanguageStr ) ) {
        key.Printf( _T( "/auto_complete/entry%d/language" ), count );
        Manager::Get()->GetConfigManager( _T( "editor" ) )->Write( key, langStr );
      }
      key.Printf( _T( "/auto_complete/entry%d/name" ), count );
      Manager::Get()->GetConfigManager( _T( "editor" ) )->Write( key, it->first );
      key.Printf( _T( "/auto_complete/entry%d/code" ), count );
      Manager::Get()->GetConfigManager( _T( "editor" ) )->Write( key, code );
    }
  }
}

void Abbreviations::EditorEventHook( M_Code_Editor* editor, wxScintillaEvent& event ) {
  M_Code_StyledTextCtrl* control = editor->GetControl();
  if( !IsAttached() || !m_IsAutoCompVisible || !control ) {
    event.Skip();
    return;
  }
  if( event.GetEventType() == wxEVT_SCI_AUTOCOMP_SELECTION ) {
    const wxString& itemText = event.GetText();
    int curPos = control->GetCurrentPos();
    int startPos = control->WordStartPosition( curPos, true );
    const int endPos = control->WordEndPosition( curPos, true );
    control->BeginUndoAction();
    control->SetTargetStart( startPos );
    control->SetTargetEnd( endPos );
    control->ReplaceTarget( itemText );
    control->GotoPos( startPos + itemText.size() );
    control->EndUndoAction();
    DoAutoComplete( editor );
    event.SetText( wxEmptyString );
    event.SetEventType( wxEVT_NULL );
  } else {
    m_IsAutoCompVisible = control->AutoCompActive();
  }
  if( !m_IsAutoCompVisible ) {
    event.Skip();
  }
}

cbConfigurationPanel* Abbreviations::GetConfigurationPanel( wxWindow* parent ) {
  return new AbbreviationsConfigPanel( parent, this );
}

void Abbreviations::ClearAutoCompLanguageMap() {
  AutoCompLanguageMap::iterator it;
  for( it = m_AutoCompLanguageMap.begin(); it != m_AutoCompLanguageMap.end(); ++it ) {
    it->second->clear();
    delete it->second;
    it->second = 0;
  }
  m_AutoCompLanguageMap.clear();
}

AutoCompleteMap* Abbreviations::GetCurrentACMap( M_Code_Editor* ed ) {
  AutoCompleteMap* pAutoCompleteMap;
  EditorColourSet* colour_set = ed->GetColourSet();
  if( colour_set ) {
    wxString strLang = colour_set->GetLanguageName( ed->GetLanguage() );
    if( strLang == _T( "Fortran77" ) ) {
      strLang = _T( "Fortran" );
    }
    if( m_AutoCompLanguageMap.find( strLang ) == m_AutoCompLanguageMap.end() ) {
      pAutoCompleteMap = m_AutoCompLanguageMap[defaultLanguageStr];
    } else
    { pAutoCompleteMap = m_AutoCompLanguageMap[strLang]; }
  } else {
    pAutoCompleteMap = m_AutoCompLanguageMap[defaultLanguageStr];
  }
  return pAutoCompleteMap;
}
